package aconcagua

import aconcagua.measure.ArithmeticObject;
import aconcagua.measure.Measure
import aconcagua.measure.MeasureBag;
import aconcagua.measure.NullUnit;
import aconcagua.measure.Infinity;
import aconcagua.measure.MinusInfinity;
import aconcagua.measure.UnitBehavior;

class Aconcagua {

	// Infinity objects
	static Infinity INFINITY = new Infinity();
	static MinusInfinity MINUS_INFINITY = new MinusInfinity();

	public static initializeModel() {
		extendGroovyMetaClassesBehavior();
	}
	
	private static extendGroovyMetaClassesBehavior() {
		
		ExpandoMetaClass.enableGlobally();

		// extends Number behavior
		Number.metaClass {
			// accessing
			amount { -> delegate };
			baseUnit { -> new NullUnit() };
			unit { -> delegate.baseUnit() };
			unitNameOf { arg -> delegate.abs() == 1 ? arg.nameForOne() : arg.nameForMany() };
			numerator { -> delegate };
			denominator { -> 1 };

			// arithmetic operations
			plus { arg ->
				def metaMethod = delegate.metaClass.getMetaMethod("plus", arg);
				
				if (arg instanceof ArithmeticObject) arg.plus(delegate)
				else metaMethod.doMethodInvoke(delegate, arg) };
			
			minus { arg ->
				def metaMethod = delegate.metaClass.getMetaMethod("minus", arg);
				
				if (arg instanceof ArithmeticObject) arg.negate().plus(delegate)
				else metaMethod.doMethodInvoke(delegate, arg) };
			
			div { arg ->
				def metaMethod = delegate.metaClass.getMetaMethod("div", arg);
				
				if (arg instanceof ArithmeticObject) arg.divideFrom(delegate)
				else metaMethod.doMethodInvoke(delegate, arg) };
			
			intdiv { arg ->
				def metaMethod = delegate.metaClass.getMetaMethod("intdiv", arg);
				
				if (arg instanceof ArithmeticObject) arg.integerDivideFrom(delegate)
				else metaMethod.doMethodInvoke(delegate, arg) };
			
			mod { arg ->
				def metaMethod = delegate.metaClass.getMetaMethod("mod", arg);
				
				if (arg instanceof ArithmeticObject) arg.moduloFrom(delegate)
				else metaMethod.doMethodInvoke(delegate, arg) };
			
			multiply { arg ->
				def metaMethod = delegate.metaClass.getMetaMethod("multiply", arg);
				
				if (arg instanceof ArithmeticObject || arg instanceof UnitBehavior) arg.multiply(delegate)
				else metaMethod.doMethodInvoke(delegate, arg) };
			
			// arithmetic operations - aconcagua
			addMeasure { arg -> MeasureBag.with(arg, delegate) };
			
			divideMeasure { arg -> new Measure(arg.amount() / delegate, arg.unit()) };
			
			divideMeasureBag { arg -> arg.divideByMeasure(delegate) };
			
			moduloMeasure { arg -> new Measure(arg.amount() % delegate, arg.unit()) };
			
			multiplyMeasure { arg -> new Measure(arg.amount() * delegate, arg.unit()) };
			
			divideByUnit { arg -> arg.with(1 / delegate) };
			
			multiplyByUnit { arg -> arg.with(delegate) };
			
			// bag creation
			createBagWithMeasure { arg -> delegate.isNothing() ? arg : arg.createBagWithNumber(delegate) };
			
			// comparing
			equalsMeasure { arg -> delegate.isNothing() & arg.isNothing() };
			
			greaterThanMeasure { arg ->
				if (delegate.isNothing()) arg < arg.unit().nullMeasure()
				else (arg.convertTo(delegate.baseUnit())).amount() < delegate };
			
			lessThanMeasure { arg ->
				if (delegate.isNothing()) arg > arg.unit().nullMeasure()
				else (arg.convertTo(delegate.baseUnit())).amount() > anObject };
			
			// converting
			convertToBaseUnit { -> delegate };
			
			// testing
			isMeasureBag { -> false };
			isNothing { -> delegate == 0 };
			isSomethig { -> !delegate.isNothing() };
			sameDomainAs { arg -> delegate.unit().sameDomainAs(arg.unit()) };
		}
		
		// extends BigDecimal behavior
		BigDecimal.metaClass {
			// truncating
			trunc { -> delegate.toBigInteger() };
		}
		
		// extends Object behavior
		Object.metaClass {
			// comparing
			equalsMeasure { arg -> false };
			
			equalsMeasureBag { arg -> false };
			
			// testing
			isNothing { -> false };
		}
	}
}
